
#include "EggNinePCH.h"

#include "simpleMesh.h"
#include "presentTechnique.h"

#include "assimp/Importer.hpp"
#include "assimp/scene.h"
#include "assimp/postprocess.h"
#include "assimp/mesh.h"
#include "assimp/types.h"
#include "assimp/material.h"

using namespace std;
using namespace cimg_library;

namespace EggNine {

SimpleMesh::SimpleMesh(const string& fileName)
    : PresentableMesh(PRESENT_SIMPLE)
{
    Assimp::Importer importer;

    const aiScene* pScene = importer.ReadFile(fileName, aiProcess_Triangulate | aiProcess_GenNormals);

    if(!pScene)
        cerr<<"Error parsing file: "<<fileName<<endl;

    const aiMesh* paiMesh = pScene->mMeshes[0];
    
    glGenVertexArrays(1, &m_vaoId);
    glGenBuffers(1, &m_vbo);
    glGenBuffers(1, &m_indxBuff);

    vertex *vertices;

    vertices = new vertex[paiMesh->mNumVertices];
    for(unsigned i=0;i<paiMesh->mNumVertices;i++) {
        memcpy(vertices[i].pos, paiMesh->mVertices+i, member_size(vertex, pos));
        memcpy(vertices[i].norm, paiMesh->mNormals+i, member_size(vertex, norm));
        memcpy(vertices[i].tex, paiMesh->mTextureCoords[0]+i, member_size(vertex, tex));
    }
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertex)*paiMesh->mNumVertices, vertices, GL_STATIC_DRAW);
    delete[] vertices;

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indxBuff);
    vector<unsigned short> indices;
    for(unsigned i=0;i<paiMesh->mNumFaces;i++) {
        assert(paiMesh->mFaces[i].mNumIndices==3);
        indices.push_back(paiMesh->mFaces[i].mIndices[0]);
        indices.push_back(paiMesh->mFaces[i].mIndices[1]);
        indices.push_back(paiMesh->mFaces[i].mIndices[2]);
    }
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*indices.size(), &indices[0], GL_STATIC_DRAW);
    m_indices=(unsigned)indices.size();

    if(pScene->mMaterials[paiMesh->mMaterialIndex]->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
        aiString txPath;
        pScene->mMaterials[paiMesh->mMaterialIndex]->GetTexture(aiTextureType_DIFFUSE, 0, &txPath);
        
        CImg<unsigned char> imgDiffuse(txPath.C_Str());
        int texWidht=imgDiffuse.width();
        int texHeight=imgDiffuse.height(); 
        imgDiffuse.channels(0, 2);
        imgDiffuse.mirror('y');
        imgDiffuse.permute_axes("cxyz");

        glGenTextures(1, &m_diffTex);
        glBindTexture(GL_TEXTURE_2D, m_diffTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, texWidht, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, imgDiffuse.data());
        glGenerateMipmap(GL_TEXTURE_2D);
    }

    m_material.diffuse=vec4(1.f, 1.f, 1.f, 1.f);
}

SimpleMesh::~SimpleMesh()
{
    glDeleteBuffers(1, &m_vbo);
    glDeleteBuffers(1, &m_indxBuff);
    glDeleteVertexArrays(1, &m_vaoId);
}

void SimpleMesh::AttachUniforms(const UniformMap& uBlocks)
{
    m_uniformModel=static_cast<const ModelBlock*>(uBlocks.at("Model"));
    m_uniformMaterial=static_cast<const MaterialBlock*>(uBlocks.at("MaterialAttrs"));
}

void SimpleMesh::MapAttributes(const ProgramsMap& tMap)
{
    const IProgram* pEffect=tMap.at("SimplePresent");
    int pos=pEffect->GetAttribLocation("Position");
    int norm=pEffect->GetAttribLocation("Normal");
    int tex=pEffect->GetAttribLocation("TexCoords");

    glBindVertexArray(m_vaoId);

    glVertexAttribFormat(pos, member_size(vertex, pos)/sizeof(float), GL_FLOAT, GL_FALSE, member_offset(vertex, pos));
    glVertexAttribFormat(norm, member_size(vertex, norm)/sizeof(float), GL_FLOAT, GL_FALSE, member_offset(vertex, norm));
    glVertexAttribFormat(tex, member_size(vertex, tex)/sizeof(float), GL_FLOAT, GL_FALSE, member_offset(vertex, tex));

    glVertexAttribBinding(pos, 0);
    glVertexAttribBinding(norm, 0);
    glVertexAttribBinding(tex, 0);

    glBindVertexBuffer(0, m_vbo, 0, sizeof(vertex));

    glEnableVertexAttribArray(pos);
    glEnableVertexAttribArray(norm);
    glEnableVertexAttribArray(tex);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indxBuff);
    
    glBindVertexArray(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void SimpleMesh::Draw(const IProgram* pTechnique)
{
    m_uniformModel->SetData(&m_modelData);
    m_uniformModel->Update();
    m_uniformMaterial->SetData(&m_uniformMaterial);
    m_uniformMaterial->Update();

    static_cast<const PresentTechnique*>(pTechnique)->SetDiffuseTexture(m_diffTex);

    glBindVertexArray(m_vaoId);
    glDrawElements(GL_TRIANGLES, m_indices, GL_UNSIGNED_SHORT, NULL);
}

} // EggNine